package vn.nt.healthcare.server.ws.controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import javapns.Push;
import javapns.communication.exceptions.CommunicationException;
import javapns.communication.exceptions.KeystoreException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.google.android.gcm.server.Constants;
import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.MulticastResult;
import com.google.android.gcm.server.Result;
import com.google.android.gcm.server.Sender;

import vn.nt.healthcare.server.ws.domain.AppConfig;
import vn.nt.healthcare.server.ws.service.AppConfigService;
import vn.nt.healthcare.server.ws.service.PushService;

@Controller
public class PushController {
	protected final Logger logger = Logger.getLogger(getClass().getName());
	private static final int MULTICAST_SIZE = 1000;
	private static final Executor threadPool = Executors.newFixedThreadPool(5);
	private Sender sender;

	@Autowired
	private PushService pushService;

	@Autowired
	private AppConfigService appConfigService;

	@RequestMapping(value = "/push/register", method = RequestMethod.POST)
	@ResponseBody
	public String registerPush(
			HttpServletRequest request,
			@RequestParam(value = "companyId", required = true) String companyId,
			@RequestParam(value = "token", required = true) String token,
			@RequestParam(value = "os", required = true) String os,
			@RequestParam(value = "customerId", required = true) int customerId) {
		boolean result = pushService.registerPushFunction(companyId, token, os,
				customerId);
		if (result) {
			return "Thank you for using this function";
		} else {
			return "Registered failed";
		}
	}

	@RequestMapping(value = "/push/delete", method = RequestMethod.DELETE)
	@ResponseBody
	public String deletePush(
			HttpServletRequest request,
			@RequestParam(value = "companyId", required = true) String companyId,
			@RequestParam(value = "id", required = true) int id) {
		boolean result = pushService.destroyPushFunction(companyId, id);
		if (result) {
			return "Destroy this service successfully";
		} else {
			return "Destroy this service failed";
		}
	}

	@SuppressWarnings("unchecked")
	@RequestMapping(value = "/push/pushnotification", method = RequestMethod.GET)
	@ResponseBody
	public String pushAll(
			HttpServletRequest request,
			@RequestParam(value = "companyId", required = true) String companyId,
			@RequestParam(value = "message", required = true) String message,
			@RequestParam(value = "app") String appname) {
		Object[] results = pushService.findAllPush(companyId);
		if (results == null) {
			return "No devices";
		}
		AppConfig appConfig = appConfigService.findByAppName(appname);
		List<String> listDeviceIos = (List<String>) results[0];
		List<String> listDeviceAndroid = (List<String>) results[1];
		try {
			return push(message, listDeviceIos, listDeviceAndroid, appConfig);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "Error";
	}

	@SuppressWarnings("unchecked")
	@RequestMapping(value = "/push/pushnotificationbydoctor", method = RequestMethod.GET)
	@ResponseBody
	public String pushDoctor(
			HttpServletRequest request,
			@RequestParam(value = "companyId", required = true) String companyId,
			@RequestParam(value = "message", required = true) String message,
			@RequestParam(value = "fromDate", required = true) Date fromDate,
			@RequestParam(value = "toDate", required = true) Date toDate,
			@RequestParam(value = "staffId", required = true) int staffId,
			@RequestParam(value = "app") String appname) {
		Object[] results = pushService.findPushesByDoctor(companyId, staffId,
				fromDate, toDate);
		if (results == null) {
			return "No devices";
		}
		AppConfig appConfig = appConfigService.findByAppName(appname);
		List<String> listDeviceIos = (List<String>) results[0];
		List<String> listDeviceAndroid = (List<String>) results[1];
		try {
			return push(message, listDeviceIos, listDeviceAndroid, appConfig);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "Error";
	}

	public String push(String message, List<String> devicesIOS,
			List<String> deviceAndroid, AppConfig appConfig) throws IOException {
		if (appConfig == null)
			return "error";
		if (!CollectionUtils.isEmpty(devicesIOS)) {
			try {
				Push.combined(message, 1, "", appConfig.getIosKeyStore(),
						appConfig.getIosPassword(),
						appConfig.isIosProduction(), devicesIOS);
				logger.info("send to ios finished");
			} catch (CommunicationException e) {
				e.printStackTrace();
			} catch (KeystoreException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		String status = "";
		if (!CollectionUtils.isEmpty(deviceAndroid)) {
			sender = new Sender(appConfig.getGoogleAPIKey());
			if (deviceAndroid.size() == 1) {
				String registrationId = deviceAndroid.get(0);
				Message messages = new Message.Builder().addData("message",
						message).build();
				Result result = sender.send(messages, registrationId, 5);
				status = "Sent message to one device: " + result;
			} else {
				int total = deviceAndroid.size();
				List<String> partialDevices = new ArrayList<String>(total);
				int counter = 0;
				int tasks = 0;
				for (String device : deviceAndroid) {
					counter++;
					partialDevices.add(device);
					int partialSize = partialDevices.size();
					if (partialSize == MULTICAST_SIZE || counter == total) {
						asyncSend(partialDevices, message);
						partialDevices.clear();
						tasks++;
					}
				}
				status = "Asynchronously sending " + tasks
						+ " multicast messages to " + total + " devices";
			}
			logger.info(status);
		} else {
			status = "Message ignored as there is no device registered!";
		}
		return "";
	}

	private void asyncSend(List<String> partialDevices, final String text) {
		final List<String> devices = new ArrayList<String>(partialDevices);
		threadPool.execute(new Runnable() {
			public void run() {
				Message message = new Message.Builder()
						.addData("message", text).build();
				MulticastResult multicastResult;
				try {
					multicastResult = sender.send(message, devices, 5);
				} catch (IOException e) {
					logger.log(Level.SEVERE, "Error posting messages", e);
					return;
				}
				List<Result> results = multicastResult.getResults();
				// analyze the results
				for (int i = 0; i < devices.size(); i++) {
					String regId = devices.get(i);
					Result result = results.get(i);
					String messageId = result.getMessageId();
					if (messageId != null) {
						logger.fine("Succesfully sent message to device: "
								+ regId + "; messageId = " + messageId);
						String canonicalRegId = result
								.getCanonicalRegistrationId();
						if (canonicalRegId != null) {
							// same device has more than on registration id:
							// update it
							logger.info("canonicalRegId " + canonicalRegId);
						}
					} else {
						String error = result.getErrorCodeName();
						if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
							// application has been removed from device -
							// unregister it
							logger.info("Unregistered device: " + regId);
						} else {
							logger.severe("Error sending message to " + regId
									+ ": " + error);
						}
					}
				}
			}
		});
	}
}
